﻿using Grow.Domain.EventBus.Cap.SubscriberDomainEvents;
using Grow.Domain.Repository;
using Grow.Module.Service;

namespace Grow.Domain.EventBus.Cap.PublisherDomainEvents
{
    public class PublisherDomainEventService : IPublisherDomainEventService
    {

        private readonly IServiceContext _serviceContext;
        private readonly DomainEventBusOptions _domainEventBusOptions;
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly IPublisherDomainEventDomainRepository _publisherDomainEventDomainRepository;
        public PublisherDomainEventService(IServiceContext serviceContext)
        {
            _serviceContext = serviceContext;
            _domainEventBusOptions = _serviceContext.GetOptions<DomainEventBusOptions>();
            _unitOfWorkManager = _serviceContext.GetRequiredService<IUnitOfWorkManager>();
            _publisherDomainEventDomainRepository = _serviceContext.GetRequiredService<IPublisherDomainEventDomainRepository>();
        }

        public async Task<bool> LockPublisherEventAsync(Guid Id, CancellationToken cancellationToken)
        {
            DateTimeOffset now = DateTimeOffset.Now;
            DateTimeOffset lockEndTime = DateTimeOffset.Now.AddSeconds(_domainEventBusOptions.LockTime);
            IUnitOfWork unitOfWork = _unitOfWorkManager.GetUnitOfWork();

            PublisherDomainEvent? publisherDomainEvent = await _publisherDomainEventDomainRepository.FirstOrDefaultAsync(a =>
                 a.Id == Id
                && (a.LockEndTime < now || a.LockEndTime == null), cancellationToken);

            if (publisherDomainEvent is null)
            {
                return false;
            }
            publisherDomainEvent.LockEndTime = lockEndTime;
            await _publisherDomainEventDomainRepository.UpdateAsync(publisherDomainEvent, cancellationToken);

            await unitOfWork.CommitAsync(cancellationToken);
            return true;
        }

        public async Task UpdatePublisherEventAsync(Guid Id, PublisherDomainEventStatus publisherDomainEventStatus, CancellationToken cancellationToken)
        {

            IUnitOfWork unitOfWork = _unitOfWorkManager.GetUnitOfWork();

            PublisherDomainEvent? publisherDomainEvent = await _publisherDomainEventDomainRepository.FirstOrDefaultAsync(a =>
                a.Id == Id
               , cancellationToken);

            if (publisherDomainEvent is null)
            {
                return;
            }
            publisherDomainEvent.EventStatus = publisherDomainEventStatus;
            publisherDomainEvent.RetryCount++;
            if (publisherDomainEventStatus == PublisherDomainEventStatus.Success)
            {
                publisherDomainEvent.ExpireTime = DateTimeOffset.Now.AddHours(_domainEventBusOptions.SucceedExpireHour);
            }
            await _publisherDomainEventDomainRepository.UpdateAsync(publisherDomainEvent, cancellationToken);

            await unitOfWork.CommitAsync(cancellationToken);
        }

        public async Task ExpirerPublisherDomainEventAsync( CancellationToken cancellationToken)
        {

            DateTimeOffset now = DateTimeOffset.Now;
            IUnitOfWork unitOfWork = _unitOfWorkManager.GetUnitOfWork();

            List<PublisherDomainEvent> publisherDomainEvents = await _publisherDomainEventDomainRepository.ToListAsync(a =>
                a.ExpireTime < now && a.ExpireTime != null
                && a.EventStatus == PublisherDomainEventStatus.Success
               , cancellationToken);

            foreach(PublisherDomainEvent publisherDomainEvent in publisherDomainEvents)
            {
                await _publisherDomainEventDomainRepository.RemoveAsync(publisherDomainEvent, cancellationToken);
            }
            await unitOfWork.CommitAsync(cancellationToken);
        }

        public async Task<List<PublisherDomainEventDto>> GetPublisherDomainEventsOfNeedRetryAsync(CancellationToken cancellationToken)
        {
            DateTimeOffset now = DateTimeOffset.Now;
            DateTimeOffset sendAt = now.AddSeconds(-_domainEventBusOptions.StartRetryAfter);

            List<PublisherDomainEvent> publisherDomainEvents = await _publisherDomainEventDomainRepository.ToListAsync(a =>
                a.SendAt < sendAt
                && a.EnvironmentName == _domainEventBusOptions.EnvironmentName
                && a.RetryCount < _domainEventBusOptions.RetryLimitCount
                && (a.LockEndTime < now || a.LockEndTime == null)
                && (a.EventStatus == PublisherDomainEventStatus.Wait || a.EventStatus == PublisherDomainEventStatus.Fail)
               , _domainEventBusOptions.RetryFailedMax, cancellationToken);

            return publisherDomainEvents.Select(a =>
            new PublisherDomainEventDto(
                a.Id,
                a.EventName,
                a.EventBody,
                a.EventStatus,
                a.RetryCount,
                a.SendAt,
                a.LockEndTime,
                a.ExpireTime)).ToList();
        }
    }
}
